package ajustecurvas.utils;

import java.util.ArrayList;

public class MetodoGaussSeidel{
	
	public ArrayList<Double> erroIterativo;
	
	public MetodoGaussSeidel(){
		erroIterativo = new ArrayList<Double>();
	}
	
   /**
    * Método de resolucao de Gauss-Sidel propriamente dito
    * @param coeficientes
    * @param vetXZero
    * @param termos
    * @param erro
    * @return
    */
	public ArrayList<double[]> resolverSistema(double[][] coeficientes, double[] vetXZero, double[] termos, double erro){
		
	double[] vetSolucao = vetXZero; 
	double[] vetIndependentes = getVetTermosDiag(coeficientes, termos); // Vetor dos termos independentes ja divididos pela diagonal
	double[][] matDiagonal = getMatCoeficientesDiagEDiv(coeficientes); // Matriz contendo todos os termos com a diagonal nula
	double[] vetSolucAnterior = vetSolucao.clone(); // Vetor de resultados da iteraçao anterior
	ArrayList<double[]> resultados = new ArrayList<double[]>();
		
	//vetSolucAnterior = vetSolucao.clone();
	vetSolucao = calculaNovoVetSolucao(vetSolucao, vetIndependentes, matDiagonal);
	resultados.add(vetSolucao);
		
	while (checaCondicaoParada(vetSolucao, vetSolucAnterior, erro)) {
		vetSolucAnterior = vetSolucao.clone();
		vetSolucao = calculaNovoVetSolucao(vetSolucao, vetIndependentes, matDiagonal);
		resultados.add(vetSolucao);
	}  
		return resultados;
}
	
    /**
	 * Calcula os valores novo vetor solucao para a iteracao atual.
	 * @param matrixX: vetor solucao da iteração atual
	 * @param vetIndependentes: vetor de termos independentes
	 * @param matDiagonal: matriz com zero nas diagonais
	 * @return novo vetor solucao para a iteracao atual
	 */
	private double[] calculaNovoVetSolucao(double[] vetSoluc, double[] vetIndependentes, double[][] matDiagonal) {
		int lin = matDiagonal.length;
		int col = lin;
		double[] vetSolucNova = vetSoluc.clone();

		for (int i = 0; i < lin; i++) {
			double temp = 0.0;
			for (int j = 0; j < col; j++) {
				temp = temp + ((matDiagonal[i][j] * vetSolucNova[j]));
			}
			temp = temp + vetIndependentes[i];
			vetSolucNova[i] = temp; // Com isso na proxima iteracao sera usado o valor atual de Xi
		}

		return vetSolucNova;
	}

	/**
	 * Método para criar o vetor dos termos independentes divididos pela diagonal.
	 * Isso representa o isolamento de cada variavel
	 * @param coeficientes
	 * @param termos
	 * @return
	 */
	private double[] getVetTermosDiag(double[][] coeficientes, double[] termos) {
		double[] mat = new double[termos.length];
		for (int i = 0; i < termos.length; i++) {
			mat[i] = termos[i] / coeficientes[i][i];
		}

		return mat;
	}

	/**
	 * Cria e retorna a matriz contendo todos os termos dividos pela diagonal, e com a diagonal nula.
	 * @param coeficientes
	 * @return matriz diagonal
	 */
	public double[][] getMatCoeficientesDiagEDiv(double[][] coeficientes) {
		int lin = coeficientes.length;
		int col = lin;

		double[][] matDiagonal = new double[lin][col];
		for (int i = 0; i < lin; i++) {
			for (int j = 0; j < col; j++) {
				double temp = (-1) * coeficientes[i][j] / coeficientes[i][i]; //quando se isola uma variavel todos os termos 
				if (i != j)													  //sao multiplicados e dividos pelo coeficiente da variavel isolada	
					matDiagonal[i][j] = temp;
			}
			matDiagonal[i][i] = 0.0;
		}
		return matDiagonal;
	}

	/**
	 * Verifica condicao de paradas para as iteracoes.
	 * @param vetSoluc
	 * @param vetSolucAnterior
	 * @param erro
	 * @return se a tolerancia é maior ou não do que o erro
	 */
	private boolean checaCondicaoParada(double[] vetSoluc, double[] vetSolucAnterior, double erro) {
		
		double distanciaEntreDuasIteracoes = getMaiorDistanciaItAtualEAnterior(vetSoluc, vetSolucAnterior);
		double distanciaRelativa = getDistanciaRelativa(vetSoluc, distanciaEntreDuasIteracoes);
		erroIterativo.add(distanciaRelativa);
		
		if (distanciaRelativa > erro) {
			return true;
		}
		return false;
	}

	/**
	 * Retorna a maior distancia entre duas iteracoes.
	 * @param vetSoluc: vetor solucao da iteracao atual
	 * @param vetSolucAnterior: vetor solucao da iteracao anterior
	 * @return distancia absoluta entre o vetor solucao atual e anterior
	 */
	private double getMaiorDistanciaItAtualEAnterior(double[] vetSoluc,double[] vetSolucAnterior) {
		double maiorDistancia = 0.0;
		double distancia = 0.0;
		int tamanho = vetSoluc.length;
		for (int i = 0; i < tamanho; i++) {
			distancia = Math.abs(vetSoluc[i] - vetSolucAnterior[i]);
			if (distancia > maiorDistancia) {
				maiorDistancia = distancia;
			}
		}
		return maiorDistancia;
	}
	
	/**
	 * Retorna a distancia relativa entre duas iteracoes.
	 * @param vetSoluc
	 * @param distanciaEntreDuasIteracoes
	 * @return distancia relativa do vetor solucao para com a distancia entre as duas iterações
	 */
	private double getDistanciaRelativa(double[] vetSoluc, double distanciaEntreDuasIteracoes) {
		
		double maiorElemento = 0;
		double maiorValor = 0;
		double distanciaRelativa = 0;
		
		for (int i = 0; i < vetSoluc.length; i++) {
			maiorValor = Math.abs(vetSoluc[i]);
			if (maiorValor > maiorElemento) {
				maiorElemento = maiorValor;
			}

		}
		distanciaRelativa = distanciaEntreDuasIteracoes / maiorElemento;
		return distanciaRelativa;
	}

	public ArrayList<Double> getErroIterativo() {
		return erroIterativo;
	}

	public void setErroIterativo(ArrayList<Double> erroIterativo) {
		this.erroIterativo = erroIterativo;
	}
	
	
}
